package tests;

import org.junit.Test;

import java.util.LinkedList;

import static junit.framework.Assert.assertEquals;


public abstract class CommonTest {

    protected abstract String run(String q);

    @Test
    public void testBasicArithmetic() throws Exception {
        //very basic
        assertEquals("2+2=4", "4", run("2+2"));
        assertEquals("2*2=4", "4", run("2*2"));
        assertEquals("5-1", "4", run("5-1"));
        assertEquals("64/16", "4", run("64/16"));
        assertEquals("7%4", "3", run("7%4"));
        assertEquals("1+(1+(1+(1+(1+(1-2)))))", "4", run("1+(1+(1+(1+(1+(1-2)))))"));
        assertEquals("1+2+3", "6", run("1+2+3"));
        //
    }

    @Test
    public void testOperationPriority() throws Exception {

        assertEquals("2+2*2", "6", run("2+2*2"));
        assertEquals("(2+2)*2", "8", run("(2+2)*2"));
        assertEquals("5%3+1", "3", run("5%3+1"));
        assertEquals("1 == 2*3-5", "1", run("1 == 2*3-5"));
        assertEquals("2<2", "0", run("2<2"));
        assertEquals("2<=2", "1", run("2<=2"));
        assertEquals("1-1", "0", run("1-1"));

    }


    @Test
    public void someExceptions() throws Exception {
        //for the better times   assertEquals("1/0","@exception",run("1/0"));


    }

    @Test
    public void moreTests() throws Exception {
        assertEquals("2+2==4", "1", run("2+2==4"));
        assertEquals("0-2<0", "1", run("0-2<0"));
        assertEquals("0-2<0", "1", run("0-2<0"));
        assertEquals("2-5<4-2", "1", run("2-5<4-2"));
        assertEquals("5+1/2", "5", run("5+1/2"));
        assertEquals("-3+-4", "-7", run("(0-3)-4"));
        assertEquals("20-10*2-3+(2-5)", "-6", run("20-10*2-3+(2-5)"));
        assertEquals("2-3-4-5*2", "-15", run("2-3-4-5*2"));

    }

    //function tests
    @Test
    public void lambdaTests() throws Exception {

        LinkedList<TestStruct> lambdaTests = new LinkedList<>();
        lambdaTests.add(new TestStruct("let f = \\x -> x in (f 1)", "1"));
        lambdaTests.add(new TestStruct("let f = \\x -> x+x in (f 2)", "4"));
        lambdaTests.add(new TestStruct("let f = \\x -> x*5 in (f 2)", "10"));
        lambdaTests.add(new TestStruct("let f = \\x -> (2*x+5+x%2) in (f 3)", "12"));
        lambdaTests.add(new TestStruct("let f = \\x -> (2*x+5+x%2) in (let g = \\x -> (f 2 +x) in (g 3))", "12"));
        lambdaTests.add(new TestStruct("let f = \\x -> 2*x+5+x%2 in let g = \\x -> f 2 +x in g 3", "12"));
        lambdaTests.add(new TestStruct("let f = \\x -> 2*x-1 in let g = \\y -> f 2 * 3 in let h = \\x -> g 2 + f 3 + 2 in h 2", "16"));
        //
        lambdaTests.add(new TestStruct("let f = \\x -> if x > 5 then if x > 10 then 100 else 10 fi else 0 fi in (f 11)", "100"));
        lambdaTests.add(new TestStruct(" let f = \\x -> x*2 in (f (6))", "12"));

        //lambdaTests.add(new Test(" let f = \\ (x y) -> x*y in (f (6,5))", "30"));


        for (TestStruct test : lambdaTests) {
            assertEquals(test.program, test.result, run(test.program));
        }
    }

    @Test
    public void ifthenelseTests() throws Exception {
        LinkedList<TestStruct> ifthenelseTests = new LinkedList<>();
        ifthenelseTests.add(new TestStruct("if 5>2 then 10 else 1 fi", "10"));
        ifthenelseTests.add(new TestStruct("if 5<2 then 10 else 1 fi", "1"));
        ifthenelseTests.add(new TestStruct("let f =\\x -> if  x%2==0 then 0 else 1 fi in (f 11)", "1"));
        ifthenelseTests.add(new TestStruct("let f =\\x -> if  x%2==0 then 0 else 1 fi in (f 22)", "0"));

        for (TestStruct test : ifthenelseTests) {
            assertEquals(test.program, test.result, run(test.program));
        }
    }


    @Test
    public void noArgFuncTest() throws Exception {
        LinkedList<TestStruct> noArgTests = new LinkedList<>();
        noArgTests.add(new TestStruct("let f = \\ () -> 2 in (f)", "2"));
        noArgTests.add(new TestStruct("let f = \\ () -> 2 in (f*4)", "8"));
        noArgTests.add(new TestStruct("let a = \\ () -> 2 in (let b=\\ () -> 3 in (a+b))", "5"));
        noArgTests.add(new TestStruct("let a = \\ () -> 2 in (let b=\\ () -> 3 in (a*a+2*a*b+b*b))", "25"));
        for (TestStruct test : noArgTests) {
            assertEquals(test.program, test.result, run(test.program));
        }
    }

   /* @Test
    public void MultiArgumentFuncTest() throws Exception {
        LinkedList<Test> multiArgTests = new LinkedList<>();
        multiArgTests.add(new Test("let f = \\ () -> 2 in (f)", "2"));


        for (Test test : multiArgTests) {
            assertEquals(test.program, test.result, run(test.program));
        }

    }       */


    class TestStruct {
        String program;
        String result;

        TestStruct(String program, String result) {
            this.program = program;
            this.result = result;
        }
    }

}
